/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/*************************************************************************
 * File: python.cpp
 *************************************************************************
 *\author: starlove
 *************************************************************************/

#include <script/python/python.h>
#include <tools/http.h>
#include <stdio.h>
#include <stdarg.h>

CPythonMachine::CPythonMachine()
    :m_pyOnPacketFunc(NULL)
    ,m_pyOnHttpFunc(NULL)
    ,m_pyOnConnectFunc(NULL)
    ,m_pyOnCloseFunc(NULL)
#ifdef __MLSE_SCRIPT_PARALLEL__
    ,m_pyOnPacketFuncTask(NULL)
    ,m_pyOnHttpFuncTask(NULL)
    ,m_pyOnConnectFuncTask(NULL)
    ,m_pyOnCloseFuncTask(NULL)

#endif
{
}

bool CPythonMachine::Initialize(int tp /* = MACHINE_SERVER */)
{
    if(!Py_IsInitialized())
    {
        Py_Initialize();
        if(!Py_IsInitialized())
            return false;
    }

    switch (tp)
    {
    case MACHINE_SERVER:
    {
        InitModule();
    }
    break;
    case MACHINE_CLIENT:
    {
        InitClientModule();
    }
    break;
    default:
    {
        return false;
    }
    }
    return true;
}

bool CPythonMachine::Finalize()
{
    if(!Py_IsInitialized())
        return false;
    __Finalize();
    Py_Finalize();
    return true;
}

void CPythonMachine::__Finalize()
{
    PyEx_XDECREF(m_pyOnConnectFunc);
    PyEx_XDECREF(m_pyOnCloseFunc);
    PyEx_XDECREF(m_pyOnHttpFunc);
    PyEx_XDECREF(m_pyOnPacketFunc);
}

PyObject *CPythonMachine::Eval(const char *f, ...)
{
    if(!Py_IsInitialized())
        return NULL;
    char szTmp[__MLSE_SCRIPT_CMD_LIMIT__];
    va_list vl;
    va_start(vl, f);
#ifdef __MLSE_PLATFORM_WIN__
    vsnprintf_s(szTmp, __MLSE_SCRIPT_CMD_LIMIT__, __MLSE_DEBUG_INFO_LIMIT__ - 1, f, vl);
#else
    vsnprintf(szTmp, __MLSE_SCRIPT_CMD_LIMIT__ - 1, f, vl);
#endif
    va_end(vl);
    int r = PyRun_SimpleString(szTmp);
    return Py_BuildValue("i", r);
}


bool CPythonMachine::Bind(const char *pszPath, const char *pszModule)
{
    if(!Py_IsInitialized())
        return false;
    Eval("import sys\nsys.path.append('%s')", pszPath);
    PyObject *pyMod = LoadModule(pszModule);
    if(!pyMod)
        return false;
    return true;
}

PyObject *CPythonMachine::LoadModule(const char *pszModuleName)
{
    if(!Py_IsInitialized())
        return NULL;
    PyObject *pName = Py_BuildValue("s", pszModuleName);
    PyObject *pModule = PyImport_Import(pName);
    Py_XDECREF(pName);
    if(!pModule)
        PyErr_Print();
    return pModule;
}

PyObject *CPythonMachine::GetDictMember(PyObject *pyDict, const char *pszMemberName)
{
    return PyDict_GetItemString(pyDict, pszMemberName);
}

PyObject *CPythonMachine::GetModuleMember(PyObject *pyMod, const char *pszMemberName)
{
    PyObject *pyModDict = PyModule_GetDict(pyMod);
    return GetDictMember(pyModDict, pszMemberName);
}

PyObject *CPythonMachine::GetModuleMemberEx(PyObject *pyMod, PyTypeObject *pyT, const char *pszMemberName)
{
    PyObject *pyModDict = PyModule_GetDict(pyMod);
    PyObject *pyMember = GetDictMember(pyModDict, pszMemberName);
    if(!PyObject_TypeCheck(pyMember, pyT))
    {
        PyErr_Print();
        return NULL;
    }
    return pyMember;
}

PyObject *CPythonMachine::GetModuleCallbleMember(PyObject *pyMod, const char *pszMemberName)
{
    PyObject *pyMember = GetModuleMember(pyMod, pszMemberName);
    if( !PyCallable_Check(pyMember) )
    {
        PyErr_Print();
        return NULL;
    }
    return pyMember;
};

void CPythonMachine::PreSendData(LPNETWORKBUFFER buf, PyObject *pyObjData)
{
    BUFFER_WRAPPER buff;
    buff.init(m_szSendSwapBuf, __MLSE_SOCKET_SEND_SWAP_BUFFER_SIZE__);
    buff.reset();
    m_pyCodec.GetEncoder()->Encode(&buff, pyObjData);

    PACKET p;
    p.header.length = buff.data_size() + sizeof(p);
    p.header.base.flag |= ePF_ENCODE;
    p.Pack(buf, (char *)buff.data, buff.data_size());
}

PyObject *CPythonMachine::SendToPeer(int nPeerId, PyObject *pyObjData)
{
    //std::map<SOCKET, IPeer *>::iterator it = g_peers.find(static_cast<SOCKET>(nPeerId));
    //if (it == g_peers.end())
    //    Py_RETURN_FALSE;
    //IPeer *pPeer = dynamic_cast<IPeer *>(it->second);
    //if(!pPeer)
    //    Py_RETURN_FALSE;;
    //if((SOCKET)pPeer->GetIOHandle() == INVALID_SOCKET)
    //    Py_RETURN_FALSE;

    NETWORKBUFFER buf;
    PreSendData(&buf, pyObjData);
    //pPeer->SendData(buf.GetPtr(), buf.GetDataSize());
    SendToPeer(nPeerId, buf.GetPtr(), buf.GetDataSize());
    Py_RETURN_TRUE;
}

PyObject *CPythonMachine::SendToPeer(int nPeerId, void *data, unsigned int size)
{
	IPeer *pPeer = GetPeer(nPeerId);
    if(!pPeer)
        Py_RETURN_FALSE;
    if((SOCKET)pPeer->GetIOHandle() == INVALID_SOCKET)
        Py_RETURN_FALSE;
    pPeer->SendData((char *)data, size);

    Py_RETURN_TRUE;
}

void CPythonMachine::SendHttpRetToPeer(int nPeerId, PyObject *pyResponseHeader, const char *code, const char *data)
{
	IPeer *pPeer = GetPeer(nPeerId);
    if(!pPeer)
        return;
    if((SOCKET)pPeer->GetIOHandle() == INVALID_SOCKET)
        return;

	HTTPRESPONSE res;
    m_pyCodec.GetEncoder()->Encode(res.header.GetBuffer(), pyResponseHeader);
    if(code)
    {
    	strcpy(res.status_code, code);
    }else
    {
    	strcpy(res.status_code, "404 Not Found");
    }

    NETWORKBUFFER buff;
    buff.AppendStr(data);

    CHTTPParser::build_response(pPeer->GetSendBuffer(), &res, &buff);
	unsigned int size = 0;
	if(data)
		size = strlen(data);
    pPeer->SendData((char *)data, size);
}

IPeer *CPythonMachine::GetPeer(int nPeerId)
{
    std::map<SOCKET, IPeer *>::iterator it = g_peers.find(static_cast<SOCKET>(nPeerId));
    if (it == g_peers.end())
        return NULL;
    IPeer *pPeer = dynamic_cast<IPeer *>(it->second);
    return pPeer;
}

void CPythonMachine::SetCallback(int tp, PyObject *pFunc)
{
    if(!PyCallable_Check(pFunc))
        return;
    switch(tp)
    {
    case eCBTP_CONNECT:
    {
        DEBUG_INFO_SCRIPT("Set callback: eCBTP_CONNECT");
        Py_XDECREF(m_pyOnConnectFunc);
        m_pyOnConnectFunc = pFunc;
        Py_XINCREF(m_pyOnConnectFunc);
        break;
    }
    case eCBTP_CLOSE:
    {
        DEBUG_INFO_SCRIPT("Set callback: eCBTP_CLOSE");
        Py_XDECREF(m_pyOnCloseFunc);
        m_pyOnCloseFunc = pFunc;
        Py_XINCREF(m_pyOnCloseFunc);
        break;
    }
    case eCBTP_PACKET:
    {
        DEBUG_INFO_SCRIPT("Set callback: eCBTP_PACKET");
        Py_XDECREF(m_pyOnPacketFunc);
        m_pyOnPacketFunc = pFunc;
        Py_XINCREF(m_pyOnPacketFunc);
        break;
    }
    case eCBTP_HTTP:
    {
        DEBUG_INFO_SCRIPT("Set callback: eCBTP_HTTP");
        Py_XDECREF(m_pyOnHttpFunc);
        m_pyOnHttpFunc = pFunc;
        Py_XINCREF(m_pyOnHttpFunc);
        break;
    }
    }
}

bool CPythonMachine::OnHttp(SOCKET s, LPBUFFER_WRAPPER pBuffer)
{
    if(!Py_IsInitialized())
        return false;
    if(!m_pyOnHttpFunc)
        return false;
    PyObject *pyRequest = NULL;
    pyRequest = (PyObject *)m_pyCodec.GetDecoder()->Decode(pBuffer, pyRequest);
#ifdef __MLSE_SCRIPT_PARALLEL__
    m_pyOnHttpFuncTask = PyTasklet_New(NULL, m_pyOnHttpFunc);
    PyObject *pyParams = PyTuple_Pack(2, Py_BuildValue("i", s), Py_BuildValue("O", pyRequest));
    PyTasklet_Setup(m_pyOnHttpFuncTask, pyParams, NULL);
    PyTasklet_Run(m_pyOnHttpFuncTask);
    Py_XDECREF(pyParams);
    Py_XDECREF(m_pyOnHttpFuncTask);
    PyErr_Print();
#else

    PyObject* pObjRet = PyObject_CallFunction(m_pyOnHttpFunc, "iO", s, pyRequest);
    PyEx_XDECREF(pObjRet);
#endif
    PyEx_XDECREF(pyRequest);
    return true;
}

void *CPythonMachine::OnPacket(SOCKET s, LPPACKET pPacket)
{
    if(!Py_IsInitialized())
        return NULL;
    PyObject *pyPacket = NULL;
    BUFFER_WRAPPER buff;
    buff.setbuf(pPacket->GetData(), pPacket->header.length);
    pyPacket = (PyObject *)m_pyCodec.GetDecoder()->Decode(&buff, pyPacket);
#ifdef __MLSE_SCRIPT_PARALLEL__
    if(m_pyOnPacketFunc)
    {
        m_pyOnPacketFuncTask = PyTasklet_New(NULL, m_pyOnPacketFunc);
        PyObject *pyParams = PyTuple_Pack(2, Py_BuildValue("i", s), Py_BuildValue("O", pyPacket));
        PyTasklet_Setup(m_pyOnPacketFuncTask, pyParams, NULL);
        PyTasklet_Run(m_pyOnPacketFuncTask);
        Py_XDECREF(pyParams);
        Py_XDECREF(m_pyOnPacketFuncTask);
        PyErr_Print();
    }
    else
        DEBUG_INFO_SCRIPT("OnPacket: callback not found");
#else
    PyObject* pObjRet = PyObject_CallFunction(m_pyOnPacketFunc, "iO", s, pyPacket);
    Py_XDECREF(pObjRet);
#endif
    Py_XDECREF(pyPacket);
    return NULL;
}
void *CPythonMachine::OnConnect(SOCKET s, const char *szAddr, unsigned int uPort)
{
    if(!Py_IsInitialized())
        return NULL;
#ifdef __MLSE_SCRIPT_PARALLEL__
    if(m_pyOnConnectFunc)
    {
        m_pyOnConnectFuncTask = PyTasklet_New(NULL, m_pyOnConnectFunc);
        PyObject *pyParams = PyTuple_Pack(3, Py_BuildValue("i", s), Py_BuildValue("s", szAddr), Py_BuildValue("i", uPort));
        PyTasklet_Setup(m_pyOnConnectFuncTask, pyParams, NULL);
        PyTasklet_Run(m_pyOnConnectFuncTask);
        Py_XDECREF(pyParams);
        Py_XDECREF(m_pyOnConnectFuncTask);
        PyErr_Print();
    }
    else
        DEBUG_INFO_SCRIPT("OnConnect: callback not found");
#else
    PyObject* pObjRet = PyObject_CallFunction(m_pyOnConnectFunc, "isi", s, szAddr, uPort);
    Py_XDECREF(pObjRet);
#endif
    return NULL;
}

void *CPythonMachine::OnClose(SOCKET s)
{
    if(!Py_IsInitialized())
        return NULL;
#ifdef __MLSE_SCRIPT_PARALLEL__
    if(m_pyOnCloseFunc)
    {
        m_pyOnCloseFuncTask = PyTasklet_New(NULL, m_pyOnCloseFunc);
        PyObject *pyParams = PyTuple_Pack(1, Py_BuildValue("i", s));
        PyTasklet_Setup(m_pyOnCloseFuncTask, pyParams, NULL);
        PyTasklet_Run(m_pyOnCloseFuncTask);
        Py_XDECREF(pyParams);
        Py_XDECREF(m_pyOnCloseFuncTask);
        PyErr_Print();
    }
    else
        DEBUG_INFO_SCRIPT("OnClose: callback not found");
#else
    PyObject* pObjRet = PyObject_CallFunction(m_pyOnCloseFunc, "i", s);
    Py_XDECREF(pObjRet);
#endif
    return NULL;
}

bool CPythonMachine::InitModule()
{
    CPyModule module("MLSE", this);
    module.RegisterClass<CPyServer>("Server");
    module.RegisterClass<CPyClient>("Client");
    module.RegisterCallable("CreateTimer", CreateTimer, METH_VARARGS);
    module.RegisterCallable("DestoryTimer", DestoryTimer, METH_VARARGS);
    module.RegisterCallable("GetVersion", (PyCFunction)GetMachineVersion, METH_NOARGS);

    module.RegisterIntValue("MLSE_CBTP_ON_CONNECT",eCBTP_CONNECT);
    module.RegisterIntValue("MLSE_CBTP_ON_CLOSE",eCBTP_CLOSE);
    module.RegisterIntValue("MLSE_CBTP_ON_PACKET",eCBTP_PACKET);
    module.RegisterIntValue("MLSE_CBTP_ON_HTTP",eCBTP_HTTP);
    module.RegisterIntValue("MLSE_TIMER_TP_LOOP",eTimerTP_LOOP);
    module.RegisterIntValue("MLSE_TIMER_TP_ONCE",eTimerTP_ONCE);
    module.Embed();
    return true;
}

bool CPythonMachine::InitClientModule()
{
    CPyModule module("MLSE", this);
    module.RegisterClass<CPyClient>("Client");
    module.RegisterCallable("CreateTimer", CreateTimer, METH_VARARGS);
    module.RegisterCallable("DestoryTimer", DestoryTimer, METH_VARARGS);
    module.RegisterCallable("GetVersion", (PyCFunction)GetMachineVersion, METH_NOARGS);

    module.RegisterIntValue("MLSE_CBTP_ON_CONNECT",eCBTP_CONNECT);
    module.RegisterIntValue("MLSE_CBTP_ON_CLOSE",eCBTP_CLOSE);
    module.RegisterIntValue("MLSE_CBTP_ON_PACKET",eCBTP_PACKET);
    module.RegisterIntValue("MLSE_TIMER_TP_LOOP",eTimerTP_LOOP);
    module.RegisterIntValue("MLSE_TIMER_TP_ONCE",eTimerTP_ONCE);
    module.Embed();
    return true;
}

LPCPyClassBase CPyModule::m_pClassBase = NULL;

CPyModule::CPyModule(const char *module_name, CPythonMachine *pMachine)
{
    if(!CPyModule::m_pClassBase)
    {
        CPyModule::m_pClassBase = PyObject_NEW(CPyClassBase, CPyClassBase::GetTypeObject<CPyClassBase>());
        Py_INCREF(CPyModule::m_pClassBase);
    }
    CPyModule::m_pClassBase->pMachine = pMachine;
    m_name = module_name;
};

void CPyModule::RegisterCallable(const char *name, PyCFunction pyFunc, int flag)
{
    PyMethodDef pd;
    pd.ml_name = name;
    pd.ml_meth = pyFunc;
    pd.ml_doc = name;
    pd.ml_flags = flag;
    m_methods.push_back(pd);
}

static PyMethodDef G_NONEDEF = {NULL,NULL,0};
void CPyModule::Embed()
{
    PyDoc_STRVAR(pydoc, "MLSE Python ScriptMachine.");
    unsigned int nmsize = m_methods.size();
    PyMethodDef *pMethods = new PyMethodDef[nmsize + 1];
    PyMethodDef *pMethods_ref = pMethods;
    unsigned int n = 0;
    std::vector<PyMethodDef>::iterator it = m_methods.begin();
    while(it != m_methods.end() && n < nmsize)
    {
        memcpy(pMethods, &(*it), sizeof(PyMethodDef));
        n ++;
        it ++;
        pMethods ++;
    }
    memcpy(pMethods, &G_NONEDEF, sizeof(PyMethodDef));
    PyObject* pObjMod = Py_InitModule4(m_name, pMethods_ref, pydoc, (PyObject *)CPyModule::m_pClassBase, PYTHON_API_VERSION);
    //if(pMethods)
    //	delete[] pMethods;
    if( !pObjMod )
        return;
    std::map<const char *, int>::iterator itV = m_intValues.begin();
    while(itV != m_intValues.end())
    {
        PyModule_AddIntConstant(pObjMod,itV->first,itV->second);
        itV ++;
    }
}

void CPyTimer::OnTimer(unsigned long t)
{
    ITimer::OnTimer(t);
    if(!Py_IsInitialized())
        return;

    if(!m_pyOnTimerFunc)
    {
        DEBUG_INFO_SCRIPT("OnTimer: callback not found");
    }

#ifdef __MLSE_SCRIPT_PARALLEL__
    PyTaskletObject *pyOnTimerFuncTask = PyTasklet_New(NULL, m_pyOnTimerFunc);
    PyObject *pyParams = NULL;
    if(m_pyOnTimerParam)
        pyParams = PyTuple_Pack(1, m_pyOnTimerParam);
    PyTasklet_Setup(pyOnTimerFuncTask, pyParams, NULL);
    PyTasklet_Run(pyOnTimerFuncTask);
    Py_XDECREF(pyParams);
    Py_XDECREF(pyOnTimerFuncTask);
    PyErr_Print();

#else
    PyObject* pObjRet = NULL;
    pObjRet = PyObject_CallFunction(m_pyOnTimerFunc, "O", m_pyOnTimerParam);
    Py_XDECREF(pObjRet);
#endif
}

void CPyTimer::SetOnTimer(unsigned long i, char t, PyObject * fn, PyObject *p)
{
    __timer.interval = i;
    __timer.tp = t;
    Py_XDECREF(m_pyOnTimerFunc);
    Py_XDECREF(m_pyOnTimerParam);
    m_pyOnTimerFunc = fn;
    Py_XINCREF(m_pyOnTimerFunc);
    m_pyOnTimerParam = p;
    Py_XINCREF(m_pyOnTimerParam);
}
